using System;
using System.Collections.Generic;
using System.Text;

namespace Ltf.Diffs.Diff3Docs
{
	//nb. synced with / subset of line state
	public enum ChangeRegionState
	{
		None = 0,
		Conflict = 10,
		Merged = 104,
		UnMerged = 105,
		Resolved = 106,
		UserEdited = 150  
	}

	public enum ChangeRegionLocation
	{
		Here,
		Above,
		Below
	}

	public class ChangeRegion
	{
		public ChangeRegionState State;
		public ChangeRegionLocation Location;
		public int Start, End;
		public List<string> TheirLines = null;
		public List<string> YourLines = null;
		public List<string> Resolution = null;
		public List<string> MergedText = null;
		public List<string> PreviousText = null;  // does not appear to be used 
		public List<string> EditText = null;

		public ChangeRegion(int start, int end, List<string> theirs, List<string> yours)
		{
			this.Start = start;
			this.End = end;
			TheirLines = theirs;
			YourLines = yours;
			State = ChangeRegionState.Conflict;
			Location = ChangeRegionLocation.Here;
		}
		public ChangeRegion(int start, int end, List<string> mergedText, List<string> previousText, ChangeRegionState state, ChangeRegionLocation location)
		{
			this.Start = start;
			this.End = end;
			MergedText = mergedText;
			PreviousText = previousText;
			State = state;
			Location = location;
		}
	}

	public class Output : DiffDocument
	{
		const string OutputTextRangeDeleted = "<< Range Deleted >>";
		const string OutputTextNoContext = "<< No matching context >>";

		public Output(string fileName)
			: base(fileName) { }

		public override void LoadResults(Diff3Results results)
		{
			// showing base and server changes - added lines to keep in sync with local
			// main file based on server file.
			int blankLineOffset = 0;

			foreach (Diff3Results.Diff3Hunk hunk in results.Hunks)
			{
				bool insertOffSetAdj = hunk.LocalRange.IsAdd ? false : true;
				LineState insertLineState = hunk.IsAllDiff ? LineState.MergeTheirs : LineState.MergedOkay;
				LineState markLineState = LineState.MergeYours;
				int extraOffset = 0;

				List<string> removedLines = new List<string>();

				if (hunk.IsAllDiff && hunk.ServerRange.IsAdd)
					AddSpecialLine(hunk.LocalRange.From, ref blankLineOffset, LineState.MergeNoLinesTheirs, OutputTextRangeDeleted, true);

				if (hunk.IsAllDiff || hunk.IsServerDiff)
				{
					if (hunk.IsServerDiff && !hunk.LocalRange.IsAdd)
					{
						removedLines = RemoveLines(hunk, ref blankLineOffset);
						extraOffset = hunk.LocalRange.Interval;
					}

					if (!hunk.ServerRange.IsAdd)
						InsertChanges(hunk.LocalRange.From, insertLineState, hunk.ServerLines, ref blankLineOffset, insertOffSetAdj, extraOffset, removedLines);
				}

				if (hunk.IsAllDiff)
				{
					if (!hunk.LocalRange.IsAdd)
						MarkChanges(hunk.LocalRange.From, markLineState, hunk.LocalRange.Interval, blankLineOffset, true);

					if (hunk.LocalRange.IsAdd)
						AddSpecialLine(hunk.LocalRange.From + 1, ref blankLineOffset, LineState.MergeNoLinesTheirs, OutputTextNoContext, true);
				}
			}
		}

		public string GetAllText()
		{
			StringBuilder sb = new StringBuilder();
			foreach (Line line in Lines)
			{
				sb.AppendLine(line.Text);
			}
			return sb.ToString();
		}

		private List<string> RemoveLines(Diff3Results.Diff3Hunk hunk, ref int blankLineOffset)
		{
			//mergable lines removed
			Line before = null;
			Line after = null;
			List<string> removedLines = new List<string>();
			int oldBlankLineOffset = blankLineOffset;
			if (hunk.IsDeletion && hunk.LocalRange.From > 0)
			{
				before = Lines[hunk.LocalRange.From + blankLineOffset - 2];
				before.StateBelow = LineState.MergedOkay;
				before.DeletedLines.Below = removedLines;
			}
			for (int line = hunk.LocalRange.From; line <= hunk.LocalRange.To; line++)
			{
				removedLines.Add(Lines[hunk.LocalRange.From + oldBlankLineOffset - 1].Text);
				Lines.RemoveAt(hunk.LocalRange.From + oldBlankLineOffset - 1);
				blankLineOffset--;
			}
			if (hunk.IsDeletion && hunk.LocalRange.To < Lines.Count)
			{
				Lines[hunk.LocalRange.To + blankLineOffset].StateAbove = LineState.MergedOkay;
				after = Lines[hunk.LocalRange.To + blankLineOffset];
				after.DeletedLines.Above = removedLines;
			}
			return removedLines;
		}

		private int AddSpecialLine(int lineNo, ref int blankLineOffset, LineState lineState, string text, bool insertOffSetAdj)
		{
			Line builtline = new Line(lineState, text);
			Lines.Insert(lineNo + blankLineOffset - (insertOffSetAdj ? 1 : 0), builtline);
			blankLineOffset++;
			return 1; // no of lines added
		}

		public List<ChangeRegion> ChangeRegions;

		public int NumberOfMerges = 0;
		public int NumberOfUndoneMerges = 0;
		public int NumberOfConflicts = 0;
		public int NumberOfResolutions = 0;

		public class TypedLineCollection
		{
			Dictionary<LineState, List<string>> linesByState = new Dictionary<LineState, List<string>>();

			public void AddLine(LineState state, string line)
			{
				if (!linesByState.ContainsKey(state))
					linesByState.Add(state, new List<string>());

				linesByState[state].Add(line);
			}

			public bool ContainsState(LineState state)
			{
				return linesByState.ContainsKey(state);
			}

			public List<string> GetLinesForState(LineState state)
			{
				if (linesByState.ContainsKey(state))
					return linesByState[state];
				else
					return new List<string>();
			}

			public int GetCountForState(LineState state)
			{
				if (linesByState.ContainsKey(state))
					return linesByState[state].Count;
				else
					return 0;
			}
		}

		public void BuildChangeRegions()
		{
			ResetCounters();
			ChangeRegions = new List<ChangeRegion>();
			int i = 0;
			while (i < Lines.Count)
			{
				bool incDone = false;
				if (Lines[i].State != LineState.None)
				{
					TypedLineCollection lineCol = new TypedLineCollection();
					int start = i;
					while (i < Lines.Count && Lines[i].State != LineState.None)
					{
						lineCol.AddLine(Lines[i].State, Lines[i].Text);
						i++;
						incDone = true;
					}
					ChangeRegion cr = null;
					if (lineCol.GetCountForState(LineState.MergeTheirs) > 0 || lineCol.GetCountForState(LineState.MergeYours) > 0)
					{
						cr = (new ChangeRegion(start, i - 1, lineCol.GetLinesForState(LineState.MergeTheirs), lineCol.GetLinesForState(LineState.MergeYours)));
						NumberOfConflicts++;
					}
					foreach (LineState lineState in new LineState[] { LineState.Resolved, LineState.MergedOkay, LineState.MergeUndone })
					{
						if (lineCol.ContainsState(lineState))
						{
							cr = (new ChangeRegion(start, i - 1, lineCol.GetLinesForState(lineState),
								Lines[start].DeletedLines.Here,
								GetChangeRegionStateFromLineState(lineState),
								ChangeRegionLocation.Here));
							IncrementCounters(lineState);
						}
					}
					//if (cr.MergedText!=null)
					//  cr.PreviousText = new List<string>(cr.MergedText);
					ChangeRegions.Add(cr);
				}

				if (i >= Lines.Count)
					break;
				// this marks sections that have been deleted with special marks above and below
				if (Lines[i].StateBelow != LineState.None)
					incDone = AddRegionAboveOrBelow(ref i, Lines[i].StateBelow, Lines[i].DeletedLines.Below, ChangeRegionLocation.Below, true);

				if (Lines[i].StateAbove != LineState.None)
					incDone = AddRegionAboveOrBelow(ref i, Lines[i].StateAbove, Lines[i].DeletedLines.Above, ChangeRegionLocation.Above, (i == 0));

				if (!incDone)
					i++;
			}
		}

		private void ResetCounters()
		{
			NumberOfMerges = 0;
			NumberOfUndoneMerges = 0;
			NumberOfConflicts = 0;
			NumberOfResolutions = 0;
		}

		private ChangeRegionState GetChangeRegionStateFromLineState(LineState lineState)
		{
			ChangeRegionState crs = ChangeRegionState.None;
			if (lineState == LineState.MergedOkay || lineState == LineState.MergeUndone || lineState == LineState.Resolved)
				crs = (ChangeRegionState)lineState;
			return crs;
		}

		private bool AddRegionAboveOrBelow(ref int lineNo, LineState lineState, List<string> deletetedLines, ChangeRegionLocation location, bool incCounter)
		{
			if (incCounter) // if i>0 then this changeregion will have been counted above
				IncrementCounters(lineState);
			ChangeRegionState crs = GetChangeRegionStateFromLineState(lineState);

			ChangeRegion cr = (new ChangeRegion(lineNo, lineNo + 1, new List<string>(), deletetedLines, crs, location));
			ChangeRegions.Add(cr);
			lineNo++;
			return true;
		}

		private void IncrementCounters(LineState lineState)
		{
			switch (lineState)
			{
				case LineState.MergedOkay:
					NumberOfMerges++;
					break;
				case LineState.Resolved:
					NumberOfResolutions++;
					break;
				case LineState.MergeUndone:
					NumberOfUndoneMerges++;
					break;
			}
		}

		public bool ResolveUseX(ChangeRegion changeRegion, bool useTheirs)
		{
			List<string> x = (useTheirs ? changeRegion.TheirLines : changeRegion.YourLines);
			if (changeRegion.State == ChangeRegionState.Conflict)
			{
				for (int i = changeRegion.End - changeRegion.Start; i >= 0; i--)
				{
					Lines.RemoveAt(changeRegion.Start);
				}
				for (int i = 0; i < x.Count; i++)
				{
					Lines.Insert(changeRegion.Start + i, new Line(LineState.Resolved, x[i], changeRegion.TheirLines, changeRegion.YourLines));
				}
				if (x.Count == 0)
				{
					SetLineResolvedBelow(Lines[changeRegion.Start - 1], changeRegion);
					SetLineResolvedAbove(Lines[changeRegion.Start], changeRegion);
				}
				return true;
			}
			return false;
		}

		private void SetLineResolvedBelow(Line line, ChangeRegion conflictRegion)
		{
			line.StateBelow = LineState.Resolved;
			line.ResolvedLines.Theirs.Below = conflictRegion.TheirLines;
			line.ResolvedLines.Yours.Below = conflictRegion.YourLines;
		}
		private void SetLineResolvedAbove(Line line, ChangeRegion conflictRegion)
		{
			line.StateAbove = LineState.Resolved;
			line.ResolvedLines.Theirs.Above = conflictRegion.TheirLines;
			line.ResolvedLines.Yours.Above = conflictRegion.YourLines;
		}

		public bool RedoMerge(ChangeRegion changeRegion)
		{
			List<string> deletedLines = new List<string>();
			if (changeRegion.State == ChangeRegionState.UnMerged)
			{
				if (changeRegion.MergedText.Count > 0)
				{
					for (int i = changeRegion.End - changeRegion.Start; i >= 0; i--)
					{
						deletedLines = Lines[changeRegion.Start].DeletedLines.Here;
						Lines.RemoveAt(changeRegion.Start);
					}
				}
				else
				{
					if (changeRegion.Location == ChangeRegionLocation.Below)
						deletedLines = Lines[changeRegion.Start].DeletedLines.Below;
					else if (changeRegion.Location == ChangeRegionLocation.Above)
						deletedLines = Lines[changeRegion.Start].DeletedLines.Above;
				}
	// I have commented out the reverse op. because it made things go backwards.
	// I guess it needed it before I rewrote a lot of this
	// I've left it in as a reminder of what use to be needed in case I find a scenario
	// that seems to still need it
	//			deletedLines.Reverse();
				InsertLinesIntoRegion(changeRegion, LineState.MergedOkay, deletedLines);

				if (deletedLines.Count == 0)
				{
					Line line = Lines[changeRegion.Start - 1];
					Lines[changeRegion.Start - 1].StateBelow = LineState.MergedOkay;
					Lines[changeRegion.Start].StateAbove = LineState.MergedOkay;
				}
				else
				{
					if (changeRegion.Location == ChangeRegionLocation.Above)
					{
						Lines[changeRegion.Start - 1].StateBelow = LineState.None;
						Lines[changeRegion.Start + deletedLines.Count].StateAbove = LineState.None;
					}
					if (changeRegion.Location == ChangeRegionLocation.Below)
					{
						Lines[changeRegion.Start].StateBelow = LineState.None;
						Lines[changeRegion.Start + deletedLines.Count + 1].StateAbove = LineState.None;
					}
				}
				return true;
			}
			return false;
		}

		public bool UndoMerge(ChangeRegion changeRegion)
		{

			List<string> deletedLines = new List<string>();
			if (changeRegion.State == ChangeRegionState.Merged)
			{
				if (changeRegion.MergedText.Count > 0)
				{
					for (int i = changeRegion.End - changeRegion.Start; i >= 0; i--)
					{
						deletedLines = Lines[changeRegion.Start].DeletedLines.Here;
						Lines.RemoveAt(changeRegion.Start);
					}
				}
				else
				{
					if (changeRegion.Location == ChangeRegionLocation.Below)
						deletedLines = Lines[changeRegion.Start].DeletedLines.Below;
					else if (changeRegion.Location == ChangeRegionLocation.Above)
						deletedLines = Lines[changeRegion.Start].DeletedLines.Above;
				}
				// I have commented out the reverse op. because it made things go backwards.
				// I guess it needed it before I rewrote a lot of this
				// I've left it in as a reminder of what use to be needed in case I find a scenario
				// that seems to still need it
				//	deletedLines.Reverse();
				InsertLinesIntoRegion(changeRegion, LineState.MergeUndone, deletedLines);

				if (deletedLines.Count == 0)
				{
					Line line = Lines[changeRegion.Start - 1];
					line.StateBelow = LineState.MergeUndone;
					line.DeletedLines.Below = changeRegion.MergedText;
					line = Lines[changeRegion.Start];
					line.StateAbove = LineState.MergeUndone;
					line.DeletedLines.Above = changeRegion.MergedText;
				}
				else
				{
					if (changeRegion.Location == ChangeRegionLocation.Above)
					{
						Lines[changeRegion.Start - 1].StateBelow = LineState.None;
						Lines[changeRegion.Start + deletedLines.Count].StateAbove = LineState.None;
					}
					if (changeRegion.Location == ChangeRegionLocation.Below)
					{
						Lines[changeRegion.Start].StateBelow = LineState.None;
						Lines[changeRegion.Start + deletedLines.Count + 1].StateAbove = LineState.None;
					}
				}
				return true;
			}
			return false;
		}

		public bool UndoResolution(ChangeRegion changeRegion)
		{
			List<string> deletedLines = new List<string>();
			List<string> resolvedLinesYours = new List<string>();
			List<string> resolvedLinesTheirs = new List<string>();
			List<string> resolvedLinesMerged = new List<string>();
			if (changeRegion.State == ChangeRegionState.Resolved)
			{
				if (changeRegion.MergedText.Count > 0)
				{
					for (int i = changeRegion.End - changeRegion.Start; i >= 0; i--)
					{
						resolvedLinesYours = Lines[changeRegion.Start].ResolvedLines.Yours.Here;
						resolvedLinesTheirs = Lines[changeRegion.Start].ResolvedLines.Theirs.Here;
						resolvedLinesMerged = Lines[changeRegion.Start].ResolvedLines.Merged.Here;
						Lines.RemoveAt(changeRegion.Start);
					}
				}
				else
				{
					if (changeRegion.Location == ChangeRegionLocation.Below)
					{
						resolvedLinesYours = Lines[changeRegion.Start].ResolvedLines.Yours.Below;
						resolvedLinesTheirs = Lines[changeRegion.Start].ResolvedLines.Theirs.Below;
						resolvedLinesMerged = Lines[changeRegion.Start].ResolvedLines.Merged.Below;
					}
					else if (changeRegion.Location == ChangeRegionLocation.Above)
					{
						resolvedLinesYours = Lines[changeRegion.Start].ResolvedLines.Yours.Above;
						resolvedLinesTheirs = Lines[changeRegion.Start].ResolvedLines.Theirs.Above;
						resolvedLinesMerged = Lines[changeRegion.Start].ResolvedLines.Merged.Above;
					}
				}
				if (resolvedLinesYours!= null)
				{
					if (resolvedLinesYours.Count == 0)
						InsertLinesIntoRegion(changeRegion, LineState.MergeNoLinesYours, new List<string>(new string[] { "<<No Context Lines>>" }));

					InsertLinesIntoRegion(changeRegion, LineState.MergeYours, resolvedLinesYours);
				}

				if (resolvedLinesTheirs != null)
				{
					if (resolvedLinesTheirs.Count == 0)
						InsertLinesIntoRegion(changeRegion, LineState.MergeNoLinesTheirs, new List<string>(new string[] { "<<Deleted Range>>" }));

					InsertLinesIntoRegion(changeRegion, LineState.MergeTheirs, resolvedLinesTheirs);
				}

				if (resolvedLinesMerged != null)
				{
					//if (resolvedLinesMerged.Count == 0)
					//  InsertLinesIntoRegion(changeRegion, LineState.MergeNoLinesTheirs, new List<string>(new string[] { "<<Deleted Range>>" }));

					if (resolvedLinesMerged.Count != 0)
						InsertLinesIntoRegion(changeRegion, LineState.MergedOkay, resolvedLinesMerged);
				}

				if (changeRegion.Location == ChangeRegionLocation.Above)
				{
					Lines[changeRegion.Start - 1].StateBelow = LineState.None;
					Lines[changeRegion.Start + resolvedLinesTheirs.Count + resolvedLinesYours.Count + 1].StateAbove = LineState.None;
				}
				if (changeRegion.Location == ChangeRegionLocation.Below)
				{
					Lines[changeRegion.Start].StateBelow = LineState.None;
					Lines[changeRegion.Start + +resolvedLinesTheirs.Count + resolvedLinesYours.Count + 1].StateAbove = LineState.None;
				}
				return true;
			}
			return false;
		}

		private void InsertLinesIntoRegion(ChangeRegion changeRegion, LineState lineState, List<string> lines)
		{
			for (int i = lines.Count - 1; i >= 0; i--)
			{
				Line newLine = new Line(lineState, lines[i]);
				newLine.DeletedLines.Here = changeRegion.MergedText;
				Lines.Insert(changeRegion.Start + ((changeRegion.MergedText.Count == 0 && changeRegion.Location != ChangeRegionLocation.Above) ? 1 : 0), newLine);
			}

		}

		public void ResolveUseAllYoursOrTheirs(bool useTheirs)
		{
			int offset = 0;
			foreach (ChangeRegion c in ChangeRegions)
			{
				if (c.State == ChangeRegionState.Conflict)
				{
					List<string> lines = (useTheirs ? c.TheirLines : c.YourLines);
					// use a copy of offset, as we'll be modifying the original as we go
					int o = offset;
					for (int i = c.End - c.Start; i >= 0; i--)
					{
						Lines.RemoveAt(c.Start + o);
						offset--;
					}
					for (int i = 0; i < lines.Count; i++)
					{
						Lines.Insert(c.Start + i + o, new Line(LineState.Resolved, lines[i], c.TheirLines, c.YourLines));
						offset++;
					}
					if (lines.Count == 0)
					{
						SetLineResolvedBelow(Lines[c.Start - 1], c);
						SetLineResolvedAbove(Lines[c.Start], c);
					}
				}
			}
			BuildChangeRegions();
		}

		public bool ResolveUseNeither(ChangeRegion changeRegion)
		{
			if (changeRegion.State == ChangeRegionState.Conflict)
			{
				for (int i = changeRegion.End - changeRegion.Start; i >= 0; i--)
				{
					Lines.RemoveAt(changeRegion.Start);
				}
				SetLineResolvedBelow(Lines[changeRegion.Start - 1], changeRegion);
				SetLineResolvedAbove(Lines[changeRegion.Start], changeRegion);
				return true;
			}
			return false;
		}

		

		public bool ResolveUseOneAfterOther(ChangeRegion changeRegion, bool useMineAfterTheirs)
		{
			if (changeRegion.State == ChangeRegionState.Conflict)
			{
				List<string> before = (useMineAfterTheirs ? changeRegion.TheirLines : changeRegion.YourLines);
				List<string> after = (useMineAfterTheirs ? changeRegion.YourLines : changeRegion.TheirLines);
				for (int i = changeRegion.End - changeRegion.Start; i >= 0; i--)
				{
					Lines.RemoveAt(changeRegion.Start);
				}
				for (int i = 0; i < before.Count; i++)
				{
					Lines.Insert(changeRegion.Start + i, new Line(LineState.Resolved, before[i], changeRegion.TheirLines, changeRegion.YourLines));
				}
				// this inserts above the previous, which is why it looks backwards
				for (int i = 0; i < after.Count; i++)
				{
					Lines.Insert(changeRegion.Start + i, new Line(LineState.Resolved, after[i], changeRegion.TheirLines, changeRegion.YourLines));
				}
				return true;
			}
			return false;
		}

		public bool ResolveUseUserEdit(ChangeRegion changeRegion)
		{
			for (int i = changeRegion.End - changeRegion.Start; i >= 0; i--)
			{
				Lines.RemoveAt(changeRegion.Start);
			}
			if (changeRegion.EditText.Count > 0)
			{
				for (int i = 0; i < changeRegion.EditText.Count; i++)
				{
					Lines.Insert(changeRegion.Start + i, new Line(LineState.Resolved, changeRegion.EditText[i],
						changeRegion.TheirLines, changeRegion.YourLines, changeRegion.PreviousText));
				}
			}
			else
			{
				SetLineResolvedBelow(Lines[changeRegion.Start - 1], changeRegion);
				SetLineResolvedAbove(Lines[changeRegion.Start], changeRegion);
			}
			return true;

		}
	}
}